/* Dumps VMs' CPU load to FIFO pipe periodically.
 * 
 *
 *
 */
#define DEBUG
#undef DEBUG

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#include <xenstat.h>

/* Macros */
#define LOCKFILE "/var/run/dumpload.pid"
#define FIFO "/var/run/xnopt/load.dat"
#define FIFO2 "/var/run/xnopt/net.dat"
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define BUFFER_SIZE 4096 /* buffer size of data write to FIFO pipe */

/* Globals 
---------------------------------------------*/
struct timeval curtime, oldtime;
xenstat_handle *xshandle = NULL;
xenstat_node *prev_node = NULL;
xenstat_node *cur_node = NULL;
int signal_exit = 0; /* if == 1, SIGINT or SIGTERM is delivered. */
struct timespec req, rem; /* required and remained time of nanosleep() */
FILE *fifo = NULL;
FILE *fifo2 = NULL;
struct cpu_pct *cpu_pcts = NULL;
struct net_stat *net_stats= NULL;
int lockfd = 0;
char domain_buffer[BUFFER_SIZE]; /* FIFO pipe write buffer */
char net_buffer[BUFFER_SIZE]; /* FIFO pipe write buffer */

/* Struct to store cpu percentile of a domain */
struct cpu_pct {
    unsigned int domid;
    double pct;
};

/* struct to store network statistics */
struct net_stat {
    unsigned int domid;
    unsigned long long rx_bytes;
    unsigned long long tx_bytes;
    unsigned long long rx_packets;
    unsigned long long tx_packets;
};

enum {RX_BYTES = 0, TX_BYTES, RX_PACKETS, TX_PACKETS};

/* Local functions 
---------------------------------------------*/

/* Get pcpu percentile of a domain */
static double get_cpu_pct(xenstat_domain *domain);

/* Clean up routine before exit 
 * release global resources */
static void cleanup(void);

/* Open a lockfile to ensure only one copy of process is running.*/
static int already_running(void);

/* Handle signals 
 * The daemon process does not respose to signal
*/
static void signal_exit_handler(int sig);

/* Export all (cpu percentile)s of all domain */
static void export_cpu_net(struct cpu_pct **cpu_pcts_pp, struct net_stat **net_stat_pp, int *num_domains);

/* Write struct cpu_pct to fifo */
static void write_cpu_pct(FILE *fifo, struct cpu_pct *cpu_pcts_p, int num_domains);

/* Write struct net_stat to fifo */
static void write_net_stat(FILE *fifo, struct net_stat *net_stat_p, int num_domains);

/* --------------------------------------------*/

int main(int argc, char *argv[]) {
    mode_t mode = 0755;
    int num_domains = 0;
    int factor = 1;
    
    if (argc != 2) {
        printf("Usage: %s n.\n n is a positive integer, meaning cycle is (n*100)ms.\n");
        exit(EXIT_FAILURE);
    }
    factor = atoi(argv[1]);
    if (factor <= 0) {
        printf("Invalid number\n");
        printf("Usage: %s n.\n n is a positive integer, meaning cycle is (n*100)ms.\n");
        exit(EXIT_FAILURE);
    }

    /* Daemonize process */
	if (daemon(0, 1) == -1) {
		perror("Error: fail to daemonize the process.\n");
	}
    openlog(NULL, LOG_PID, LOG_DAEMON);
    syslog(LOG_INFO, "start netcpud.\n");

    /* Only one daemon running */
    lockfd = already_running();

    /* */
    atexit(cleanup);

    /* */
    xshandle = xenstat_init();
    if (xshandle == NULL){
        syslog(LOG_ERR, "Fail to get xenstat handle.\n");
    }

    syslog(LOG_INFO, "Get xenstat_handle.\n");

    /* Sigaction
     * Catch SIGTERM, SIGINT, and break while loop
     */
    struct sigaction sa;
    sa.sa_handler = signal_exit_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
        syslog(LOG_ERR, "Error, fail to resigter sigactions");
        exit(EXIT_FAILURE);
    }

    /* Make an named pipe for cpu load percentiles */
    unlink(FIFO);
    unlink(FIFO2);

    int stat_fifo = mkfifo(FIFO, mode);
    if (stat_fifo != 0) {
        perror("Error, fail to make fifo.\n");
        exit(EXIT_FAILURE);
    }
    syslog(LOG_INFO, "Made FIFO.\n");

    stat_fifo = mkfifo(FIFO2, mode);
    if (stat_fifo != 0) {
        perror("Error, fail to make fifo2.\n");
        exit(EXIT_FAILURE);
    }
    syslog(LOG_INFO, "Made FIFO2.\n");

    fifo = fopen(FIFO, "w"); /* Blocked until a reader open this pipe */
    if (fifo == NULL) {
        perror("Error, fail to open fifo.\n");
        exit(EXIT_FAILURE);
    }

    fifo2 = fopen(FIFO2, "w");
    if (fifo2 == NULL) {
        perror("Error, fail to open fifo.\n");
        exit(EXIT_FAILURE);
    }

    /* Set time spec for nanosleep() */
    req.tv_sec = 0;
    req.tv_nsec = factor * 100000000; /* 100 ms */
    rem.tv_sec = 0;
    rem.tv_nsec = 0;

    /* Export cpu percentile every 100ms */
    do {
        /* Mark start time */
        gettimeofday(&curtime, NULL);

        /* Get cpu pcts of all domains */
        syslog(LOG_INFO, "Start to export cpu_net.\n");
        export_cpu_net(&cpu_pcts, &net_stats, &num_domains);
        syslog(LOG_INFO, "Exported VMs' cpu_net statistics.\n");

        /* Write cpu pcts to fifo */
        write_cpu_pct(fifo, cpu_pcts, num_domains);
        syslog(LOG_INFO, "Write cpu_pcts to FIFO.\n");

        /* Write cpu pcts to fifo */
        write_net_stat(fifo2, net_stats, num_domains);
        syslog(LOG_INFO, "Write net_stat to FIFO.\n");

        /* Start a new time cycle */
        oldtime = curtime;

        /* MORE ... */
        rem.tv_sec = 0;
        rem.tv_nsec = 0;
        syslog(LOG_INFO, "Nanosleep.\n");
        nanosleep(&req, &rem);
        /*
        while (rem.tv_nsec)
            nanosleep(&rem, &rem);
        */
        syslog(LOG_INFO, "Waked again.\n");
    } while(!signal_exit); /* do-while SIGINT or SIGTERM isn't arriving */
      
    syslog(LOG_INFO, "About to eixt.\n");
    exit(EXIT_SUCCESS);
}

/* ------------------------------------------------*/

static double get_cpu_pct(xenstat_domain *domain) {
    xenstat_domain *old_domain;
    double us_elapsed;

    if(prev_node == NULL)
        return 0.0;

    /* Get the domain's previous xenstat_domain saved in previous xenstat_node */
    old_domain = xenstat_node_domain(prev_node, xenstat_domain_id(domain));
    if(old_domain == NULL)
        return 0.0;

    /* us(mirco seconds) the domain elapsed since old time */
    us_elapsed = ((curtime.tv_sec-oldtime.tv_sec)*1000000.0
                   + (curtime.tv_usec-oldtime.tv_usec));

    /* x = ns
     * y = us_elapsed * 1000 
     * (x/y)*100
    */
    return ((xenstat_domain_cpu_ns(domain)
            - xenstat_domain_cpu_ns(old_domain))/10.0)/us_elapsed;
}

/* if flag == 0, return rx_bytes
   if flag == 1, return tx_bytes
   if flag == 2, return rx_packets
   if flag == 3, return tx_packets
 */
static unsigned long long get_rx_tx(xenstat_domain *domain, int flag) {
    int i = 0;
    xenstat_network *network;
    unsigned num_networks = 0;
    unsigned long long total = 0;

    num_networks = xenstat_domain_num_networks(domain);

    switch (flag) {
        case 0:
            for (i=0; i<num_networks; ++i) {
                network = xenstat_domain_network(domain, i);
                total += xenstat_network_rbytes(network);
            }
            break;
        case 1:
            for (i=0; i<num_networks; ++i) {
                network = xenstat_domain_network(domain, i);
                total += xenstat_network_tbytes(network);
            }
                break;
        case 2:
            for (i=0; i<num_networks; ++i) {
                network = xenstat_domain_network(domain, i);
                total += xenstat_network_rpackets(network);
            }
            break;
        case 3:
            for (i=0; i<num_networks; ++i) {
                network = xenstat_domain_network(domain, i);
                total += xenstat_network_tpackets(network);
            }
            break;
        default:
            break;
    }
    return total;
}

static void cleanup(void) {
    if (prev_node != NULL) 
        xenstat_free_node(prev_node);
    if (cur_node != NULL)
        xenstat_free_node(cur_node);
    if (xshandle != NULL)
        xenstat_uninit(xshandle);
    if (fifo != 0)
        fclose(fifo);
    if (fifo2 != 0)
        fclose(fifo2);
    if (cpu_pcts != NULL)
        free(cpu_pcts);
    if (lockfd != 0)
        close(lockfd);
    unlink(FIFO);
    unlink(FIFO2);
    syslog(LOG_INFO, "Resources is clean up.\n");
    closelog();
}

static void signal_exit_handler(int sig) {
    /* SIGINT, or SIGTERM is deliverd 
     * Clean up works would be done by atexit(cleanup)
     */
    signal_exit = 1;
}

static int already_running(void) {
	int fd;
	char buf[16] = "";

	/* open lock file */
	fd = open(LOCKFILE, O_RDWR | O_CREAT, LOCKMODE);
	if (fd < 0) {
		syslog(LOG_ERR, "can not open %s: %s\n", LOCKFILE, strerror(errno));
		exit(EXIT_FAILURE);
	}

	/* try add a record lock to lockfile */
	if (lockf(fd, F_TLOCK, 0) < 0) {
		if (errno == EACCES || errno == EAGAIN) {
			syslog(LOG_ERR, "can not lock file %s, a copy of the daemon process is already running.\n", LOCKFILE);
			close(fd);
			exit(EXIT_FAILURE);
		} else {
			syslog(LOG_ERR, "can not lock file %s: %s\n", LOCKFILE, strerror(errno));
			/* do not close(fd) */
			exit(EXIT_FAILURE);
		}
	}
	/* success to add a record lock to lockfile, and write a daemon process id to it */
	ftruncate(fd, 0);	/* empty a file */
	sprintf(buf, "%ld", (long)getpid());
	write(fd, buf, strlen(buf)+1);
    return fd;
}

static void export_cpu_net(struct cpu_pct **cpu_pcts_pp, struct net_stat **net_stat_pp, int *num_domains) {
    xenstat_domain *domain;
    unsigned int i;
    
    /* Get new xenstat_node, free prve node, and save current node to prev node */
    if (prev_node != NULL)
        xenstat_free_node(prev_node);
    prev_node = cur_node;
    cur_node = xenstat_get_node(xshandle, XENSTAT_ALL);
    if (cur_node == NULL)
        syslog(LOG_ERR, "Fail to get xenstat_node.\n");

    /* Count the number of domains */
    *num_domains = xenstat_node_num_domains(cur_node);

    /* Allocate memory for struct cpu_pct  */
    *cpu_pcts_pp = (struct cpu_pct *)calloc(*num_domains, sizeof (struct cpu_pct));
    if (*cpu_pcts_pp == NULL) {
        syslog(LOG_ERR, "Fail to allocate memory for cpu_pcts.\n");
        exit(EXIT_FAILURE);
    }
    syslog(LOG_INFO, "Allocated memory for struct cpu_pcts, num_domains = %d.\n", *num_domains);

    /* Allocate memory for struct net_stat */
    *net_stat_pp = (struct net_stat *)calloc(*num_domains, sizeof (struct net_stat));
    if (*net_stat_pp == NULL) {
        syslog(LOG_ERR, "Fail to allocate memory for net_stat.\n");
        exit(EXIT_FAILURE);
    }

    /* Initialize the domains pointers array 
     * with address of xenstat_domain in current xenstat_node
     * Get cpu percentile and write to LOADFILE 
     */
    for (i=0; i < *num_domains; ++ i) {
        domain = xenstat_node_domain_by_index(cur_node, i);
        (*cpu_pcts_pp)[i].domid = xenstat_domain_id(domain);
        (*cpu_pcts_pp)[i].pct = get_cpu_pct(domain);
        (*net_stat_pp)[i].domid = xenstat_domain_id(domain);
        (*net_stat_pp)[i].rx_bytes = get_rx_tx(domain, RX_BYTES);
        (*net_stat_pp)[i].tx_bytes = get_rx_tx(domain, TX_BYTES);
        (*net_stat_pp)[i].rx_packets = get_rx_tx(domain, RX_PACKETS);
        (*net_stat_pp)[i].tx_packets = get_rx_tx(domain, TX_PACKETS);
    }
    //syslog(LOG_INFO, "Write to cpu_pcts.\n");
}

static void write_cpu_pct(FILE *fifo, struct cpu_pct *cpu_pcts_p, int num_domains) {
    int i = 0; 

    syslog(LOG_INFO, "Start to write cpu_pcts to FIFO, num_domains = %d.\n", num_domains);
    bzero(domain_buffer, BUFFER_SIZE);
    for (i=0; i<num_domains; ++ i) {
        sprintf(domain_buffer+strlen(domain_buffer), "%d %f\n", cpu_pcts_p[i].domid, cpu_pcts_p[i].pct);
    }
#ifdef DEBUG
    syslog(LOG_DEBUG, "%s", domain_buffer);  
#endif
    fwrite(domain_buffer, sizeof (char), BUFFER_SIZE, fifo);
    free(cpu_pcts_p);
    /* fflush(fifo); */
}

static void write_net_stat(FILE *fifo, struct net_stat *net_stat_p, int num_domains) {
    int i = 0;

    syslog(LOG_INFO, "Start to write net_stat to FIFO, num_domains = %d.\n", num_domains);
    bzero(net_buffer, BUFFER_SIZE);
    for (i=0; i<num_domains; ++ i) {
        sprintf(net_buffer+strlen(net_buffer), "%d %llu %llu %llu %llu\n", \
                net_stat_p[i].domid, net_stat_p[i].rx_bytes, net_stat_p[i].tx_bytes,\
                net_stat_p[i].rx_packets, net_stat_p[i].tx_packets);
    }
#ifdef DEBUG
    syslog(LOG_DEBUG, "%s", net_buffer);  
#endif
    fwrite(net_buffer, sizeof (char), BUFFER_SIZE, fifo);
    free(net_stat_p);
}

